//
//  CurrentWeatherView.swift
//  SwiftWeather
//
//  Created by shen on 2023/8/28.
//

import UIKit
import Combine

final class CurrentWeatherViewModel {
    // MARK: Properties

    @Published private var weather: CurrentWeather?
    @Published private var location: Location?
    @Published private var temperatureUnits: TemperatureUnits

    init(weather: CurrentWeather? = nil, location: Location? = nil, temperatureUnits: TemperatureUnits) {
        self.weather = weather
        self.location = location
        self.temperatureUnits = temperatureUnits
    }

    var iconUpdatePublisher: AnyPublisher<(code: Int, dayPeriod: DayPeriod), Never> {
        codePublisher.combineLatest(dayPeriodPublisher)
            .map { (code: $0, dayPeriod: $1) }
            .eraseToAnyPublisher()
    }

    var temperaturePublisher: AnyPublisher<Temperature, Never> {
        $weather.combineLatest($temperatureUnits)
            .compactMap { weather, units in
                guard let weather else { return nil }
                return (weather, units)
            }
            .flatMap { weather, units in
                Just(weather).mapToTemperature(in: units)
            }
            .eraseToAnyPublisher()
    }

    var conditionPublisher: AnyPublisher<String, Never> {
        $weather
            .compactMap { $0?.condition?.text }
            .eraseToAnyPublisher()
    }

    var locationPublisher: AnyPublisher<String, Never> {
        $location
            .compactMap { location in
                guard let name = location?.name, let country = location?.country else { return nil }
                return "\(name), \(country)"
            }
            .eraseToAnyPublisher()
    }

    private var dayPeriodPublisher: AnyPublisher<DayPeriod, Never> {
        $weather
            .compactMap { $0?.isDay }
            .compactMap { DayPeriod(rawValue: $0) }
            .eraseToAnyPublisher()
    }

    private var codePublisher: AnyPublisher<Int, Never> {
        $weather
            .compactMap { $0?.condition?.code }
            .eraseToAnyPublisher()
    }
}

class CurrentWeatherView: BaseView, ViewModelable {
    typealias ViewModel = CurrentWeatherViewModel

    // MARK: Properties

    var viewModel: ViewModel! {
        didSet {
            viewModel.iconUpdatePublisher
                .sink { [weak self] code, dayPeriod in
                    self?.weatherIconView.image = Resources.Images.Weather.weatherIcon(
                        code: code,
                        dayPriod: dayPeriod
                    )
                }
                .store(in: &cancellables)

            viewModel.temperaturePublisher
                .map { $0.stringFormat }
                .assignToTextOnLabel(temperatureLabel)
                .store(in: &cancellables)

            viewModel.conditionPublisher
                .assignToTextOnLabel(conditionLabel)
                .store(in: &cancellables)

            viewModel.locationPublisher
                .assignToTextOnLabel(locationLabel)
                .store(in: &cancellables)
        }
    }

    private var cancellables = Set<AnyCancellable>()

    // MARK: - Views

    private lazy var mainVStack = UIStackView(
        arrangedSubviews: [
            weatherHStack,
            locationHStack
        ],
        axis: .vertical,
        spacing: 20
    )

    private lazy var weatherHStack = UIStackView(
        arrangedSubviews: [
            labelsVStack,
            weatherIconView
        ],
        axis: .horizontal,
        spacing: 20
    )

    private lazy var labelsVStack = UIStackView(
        arrangedSubviews: [
            temperatureLabel,
            conditionLabel
        ],
        axis: .vertical,
        spacing: 5
    )

    private lazy var locationHStack = UIStackView(
        arrangedSubviews: [
            locationImageView,
            locationLabel
        ],
        axis: .horizontal,
        spacing: 10,
        alignment: .center
    )

    private lazy var titleLabel: UILabel = {
        let label = UILabel()
        label.text = Resources.Strings.currentWeatherTitle
        label.font = Resources.Fonts.system(weight: .semibold)
        label.textColor = Resources.Colors.title
        return label
    }()

    private lazy var temperatureLabel: UILabel = {
        let label = UILabel()
        label.font = Resources.Fonts.system(size: 35, weight: .bold)
        return label
    }()

    private lazy var conditionLabel: UILabel = {
        let label = UILabel()
        label.font = Resources.Fonts.system(size: 16, weight: .medium)
        label.textColor = Resources.Colors.darkText
        label.numberOfLines = 2
        return label
    }()

    private lazy var locationLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 2
        label.font = Resources.Fonts.system(size: 14)
        label.textColor = .secondaryLabel
        return label
    }()

    private lazy var weatherIconView = UIImageView(contentMode: .scaleAspectFit)

    private lazy var locationImageView = UIImageView(
        image: Resources.Images.location,
        tintColor: .secondaryLabel,
        contentMode: .scaleAspectFit
    )

    // MARK: - Lyfecycle

    override func layoutSubviews() {
        layer.cornerRadius = 20
        addShadow()
    }

    // MARK: - Methods

    override func configureAppearance() {
        backgroundColor = Resources.Colors.secondaryBackground
    }

    override func setupViews() {
        addSubview(titleLabel)
        addSubview(mainVStack)
    }

    override func constraintViews() {
        weatherIconView.setContentHuggingPriority(.defaultHigh, for: .horizontal)

        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        mainVStack.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 20),
            titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),

            weatherIconView.widthAnchor.constraint(equalToConstant: 60),

            locationImageView.widthAnchor.constraint(equalToConstant: 20),
            locationImageView.heightAnchor.constraint(equalTo: locationImageView.widthAnchor),

            mainVStack.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10),
            mainVStack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
            mainVStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -40),
            mainVStack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20)
        ])
    }
}

// MARK: - Private methods

private extension CurrentWeatherView {
    func addShadow() {
        layer.shadowPath = UIBezierPath(
            roundedRect: CGRect(
                x: 0, y: 7, width: frame.width, height: frame.height + 7
            ),
            cornerRadius: 20
        ).cgPath

        layer.shadowOpacity = 0.4
        layer.shadowRadius = 10
    }
}
